#include <iostream>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <netinet/in.h>
#include <errno.h>
#include <unistd.h>
#include <ctime>
#include <stdlib.h>
#include <string.h>
#include <string>


#define MSG_LEN  60   // in bytes
#define MAX_HOPS      30
#define TIMEOUT       2
#define MAX_RETRIES   3
#define RESPONSE_LEN  1500

struct icmpheader {
    unsigned char icmp_type; // 0 - echo reply, 8 - echo request
    unsigned char icmp_code; // this is significant when sending an error message (unreach), and specifies the kind of error. again
    unsigned short int icmp_cksum;
    /* The following data structures are ICMP type specific */
    unsigned short int icmp_id; // used in echo request/reply messages, to identify the request
    unsigned short int icmp_seq;
}; /* total icmp header length: 8 bytes (=64 bits) */

struct ipheader {
    unsigned char ip_hl:4, ip_v:4; /* this means that each member is 4 bits */
    unsigned char ip_tos;
    unsigned short int ip_len;
    unsigned short int ip_id;
    unsigned short int ip_off;
    unsigned char ip_ttl;
    unsigned char ip_p;
    unsigned short int ip_sum;
    unsigned int ip_src;
    unsigned int ip_dst;
}; /* total ip header length: 20 bytes (=160 bits) */


#define BUF_LEN sizeof(icmpheader) + MSG_LEN

/////
int main(int argc, char* argv[])
{
	if (argc != 2)
	{
		std::cout <<"Usage: sudo ./my_traceroute destination"<< std::endl;
		return 0;
	}

	char *pSendBuffer = NULL, *destHostName = NULL;
	destHostName = argv [1];
	hostent *host = gethostbyname(destHostName);
    if (host == NULL) {
        perror("Wrong destination");
        return errno;
    }
    
    struct sockaddr_in destAddr, remoteAddr;
    destAddr.sin_addr.s_addr = ((in_addr*)host->h_addr)->s_addr;    
	destAddr.sin_family = AF_INET;
	destAddr.sin_port = 33434; // the 33434-33534 range.
    
    int sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
	if (sock < 0) {
		perror("socket");
		return errno;
	}
    
    icmpheader sendHdr;
    sendHdr.icmp_id = htons(1);	
	sendHdr.icmp_code = 0;	
	sendHdr.icmp_type = 8;	
    
	int TTL = 2;	
			
	fd_set fdRead;
	timeval timeInterval = {TIMEOUT, 0};
	
    std::string dest_ip( inet_ntoa(*(struct in_addr*)host->h_addr) );
	std::cout << "traceroute to " << destHostName << " (" << dest_ip << "), " << MAX_HOPS << " hops max" << std::endl;
	
	int hops = 0;
    size_t timeout_c = 0;
	while (hops++ < MAX_HOPS &&  destAddr.sin_addr.s_addr != remoteAddr.sin_addr.s_addr) {
        
		if (setsockopt (sock, IPPROTO_IP, IP_TTL, (char *)&TTL, sizeof (TTL)) != 0) {
			perror("Error occured in setting TTL");
			return errno;
		}

		pSendBuffer = new char[BUF_LEN];
		memcpy(pSendBuffer, &sendHdr, sizeof(icmpheader));
		memset(pSendBuffer + sizeof(icmpheader), '5', MSG_LEN);
		
		int counter = 0;
		ipheader ipHdr;
		bool respond = false;
        std::cout << (TTL - 1);	
		while (counter < MAX_RETRIES) {
			if ((sendto (sock, pSendBuffer, BUF_LEN, 0, 
				(struct sockaddr*)&destAddr, sizeof(struct sockaddr_in))) < 0) {
				perror("Error in sendto");
				return errno;
			}
            if (timeout_c > 1) respond = true;			
			FD_ZERO (&fdRead);
			FD_SET (sock, &fdRead);
			if ((select (sock+1, &fdRead, NULL, NULL, &timeInterval)) <= 0) {
                timeInterval.tv_sec = TIMEOUT;				
			}                
			if (FD_ISSET(sock, &fdRead)) {
				char *pRecvBuffer = new char[RESPONSE_LEN];
				socklen_t remoteAddrLen = sizeof(sockaddr_in);
				if ((recvfrom(sock, pRecvBuffer, RESPONSE_LEN, 0, (struct sockaddr*)&remoteAddr, &remoteAddrLen)) <= 0){
					printf("Error in recvfrom"); 
					return errno;
				}	
				
                respond = true;                
				memcpy(&ipHdr, pRecvBuffer, sizeof(ipheader));
				delete [] pRecvBuffer;
                break;
				
			} else { //Request timed out, trying again
                if (respond) break;
				std::cout << "\t*";
			}
			++counter;            
		}
		if (respond) {		
			in_addr in;
			in.s_addr = ipHdr.ip_src;
            if (timeout_c > 1) in.s_addr = remoteAddr.sin_addr.s_addr = destAddr.sin_addr.s_addr;
			char *nodeIpStr = inet_ntoa(in);
			struct hostent *node = gethostbyaddr((char*)&in.s_addr, sizeof(in.s_addr), AF_INET);            
			if (node == NULL) {
				std::cout << "\t" << nodeIpStr;
			} else {
				std::cout << "\t" << node->h_name;
			}
            std::cout << " (" << nodeIpStr << ")";
            timeout_c = 0;
		} else {
        	std::cout << "\tRequest timed out.";
            ++timeout_c;
        }

		std::cout << std::endl;		
		++TTL;
		delete [] pSendBuffer;
	}

	return 0;
}
